package com.mashibing.api.controller;

import com.alibaba.nacos.common.utils.StringUtils;
import com.mashibing.api.filter.CheckFilterContext;
import com.mashibing.api.form.SingleSendForm;
import com.mashibing.api.util.R;
import com.mashibing.api.vo.ResultVO;
import com.mashibing.common.constant.RabbitMQConstants;
import com.mashibing.common.enums.ExceptionEnums;
import com.mashibing.common.model.StandardSubmit;
import com.mashibing.common.util.SnowFlakeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;

/**
 * @author SNA
 * @date 2025/5/7
 * @Description
 **/
@RestController
@RequestMapping("/sms")
@Slf4j
@RefreshScope  // 是 Spring Cloud 提供的一个注解，主要用于支持 配置的热更新。它通常与 Spring Cloud Config 或 Nacos、Apollo 等配置中心 配合使用，使得应用在不重启的情况下能够动态感知并加载最新的配置信息。
public class SmsController {

    /**
     * 客户端IP地址的请求头信息，多个用逗号 , 隔开
     */
    @Value("${headers}")
    private String headers;

    /**
     * 基于请求头获取信息时，可能获取到的未知信息
     */
    private final String UNKNOWN = "unknown";

    /**
     * 如果是当前请求头获取IP地址，需要截取到第一个逗号 , 为止
     */
    private final String X_FORWARDED_FOR = "x-forwarded-for";
    
    @Autowired
    private CheckFilterContext checkFilterContext;

    @Autowired
    private SnowFlakeUtil snowFlakeUtil;

    @Autowired
    private RabbitTemplate rabbitTemplate;


    /**
     * 处理单条短信发送请求
     * 该方法接收一个单条短信发送的表单，进行参数校验，并返回相应的结果
     *
     * @param singleSendForm 单条短信发送的表单，包含发送短信所需的信息
     * @param bindingResult 参数校验结果，用于判断参数是否合法
     * @return 返回一个ResultVO对象，包含处理结果
     */
    @PostMapping(value = "/single_send", produces = "application/json;charset=UTF-8")
    /*@Validated 是 Spring 提供的一个注解，用于在 Spring 管理的 Bean 中启用参数校验（Bean Validation）功能，通常
      与 JSR 380 规范中的注解（如 @NotBlank、@NotNull、@Range 等）一起使用。
      1. 触发参数校验逻辑
            1.1 @Validated 表示对 singleSendForm 这个对象进行参数合法性校验。
            1.2 校验依据是该类中字段上标注的约束注解，例如：
                @NotBlank：非空且不能是空白字符
                @NotNull：不能为 null
                @Range(min = 0, max = 2)：必须在指定范围内
      2. 配合 BindingResult 使用获取错误信息
            2.1 如果校验失败，Spring 会将错误信息封装到紧跟其后的 BindingResult 对象中。
            2.2 开发者可以通过 bindingResult.hasErrors() 判断是否出错，并通过
            bindingResult.getFieldError().getDefaultMessage() 获取具体的错误提示。
      注：1.必须配合 @RequestBody 使用。 2.必须引入 validation 依赖。 3.错误处理需紧跟 BindingResult
     */
    public ResultVO singleSend(@RequestBody @Validated SingleSendForm singleSendForm, BindingResult bindingResult, HttpServletRequest req) {
        // 1. 参数校验
        if (bindingResult.hasErrors()) {
            String msg = bindingResult.getFieldError().getDefaultMessage();
            log.info("【接口模块-单条短信Controller】 参数不合法 msg = {}", msg);
            return R.error(ExceptionEnums.PARAMETER_ERROR.getCode(), msg);
        }
        //===========================获取真实的IP地址===================================
        log.info("req.getRemoteAddr: {}", req.getRemoteAddr());
        String ip = this.getRealIP(req);

        //===========================构建StandardSubmit，各种封装校验====================================
        StandardSubmit submit = new StandardSubmit();
        submit.setRealIP(ip);
        submit.setApikey(singleSendForm.getApikey());
        submit.setMobile(singleSendForm.getMobile());
        submit.setText(singleSendForm.getText());
        submit.setState(singleSendForm.getState());
        submit.setUid(singleSendForm.getUid());

        //===========================调用策略模式的校验链====================================
        checkFilterContext.check(submit);

        //===========================基于雪花算法生成唯一Id，并添加到StandardSubmit中，并设置发送时间===============
        submit.setSequenceId(snowFlakeUtil.nextId());
        submit.setSendTime(LocalDateTime.now());

        //===========================发送到MQ，交给策略模块处理====================================
        rabbitTemplate.convertAndSend(RabbitMQConstants.SMS_PRE_SEND, submit, new CorrelationData(submit.getSequenceId().toString()));

        //===========================没有问题，返回接收成功====================================
        return R.ok();
    }

    /**
     * 获取客户端真是的IP地址
     * @param req
     * @return
     */
    private String getRealIP(HttpServletRequest req) {

        // 1.声明真实的IP地址
        String ip;

        // 2.遍历请求头，并且通过req获取IP地址
        for (String header : headers.split(",")) {
            // 健壮性校验
            if (!StringUtils.isEmpty(header)) {
                // 基于req获取IP地址
                ip = req.getHeader(header);
                log.debug("Header {}: {}", header, req.getHeader(header));
                // 如果获取到的IP不为null，不为空字符串，并且不为unknown，就可以返回
                if (!StringUtils.isEmpty(ip) && !UNKNOWN.equalsIgnoreCase(ip)) {
                    // 判断请求头是否是x-forwarded-for
                    if (X_FORWARDED_FOR.equalsIgnoreCase(header) && ip.indexOf(",") > 0) {
                        ip = ip.substring(0, ip.indexOf(","));
                    }
                    // 返回IP地址
                    return ip;
                }
            }
        }

        // 3.如果请求头都没有获取到IP地址，直接基于传统的方式获取IP
        return req.getRemoteAddr();
    }
}
